//
//  LBHorizontalLoopView.m
//  LBHorizontalLoopView
//
//  Created by liubo on 2021/8/29.
//

#import "LBHorizontalLoopView.h"
#import <Masonry/Masonry.h>

@interface LBHorizontalLoopView () <UIScrollViewDelegate>

@property (nonatomic, strong) NSMutableDictionary *viewClsDict; //注册 view 的字典，key为view的类名，value 为identifier
@property (nonatomic, strong) NSMutableArray *reuseViews; //重用view的实例对象数组

///横向滚动的外层容器scrollView
@property (nonatomic, strong) UIScrollView *contentScrollView;

///左侧视图
@property (nonatomic, strong) LBHorizontalLoopViewCell *leftView;
///中间视图
@property (nonatomic, strong) LBHorizontalLoopViewCell *middleView;
///右边视图
@property (nonatomic, strong) LBHorizontalLoopViewCell *rightView;

///当前索引
@property (nonatomic, assign) NSInteger currentIndex;

///计时器
@property (nonatomic, strong) NSTimer *timer;

///正在拖动
@property (nonatomic, assign) BOOL isDragging;

/// 轮播图的数量
@property (nonatomic, assign) NSInteger numberOfLoopView;

@end

@implementation LBHorizontalLoopView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.restartTimeEndingDragging = YES;
        self.duration = 3;
        [self setUpUI];
        [self setUpContraints];
    }
    return self;
}

- (void)setUpUI
{
    self.clipsToBounds = YES;
    [self addSubview:self.contentScrollView];
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapClick)];
    [self addGestureRecognizer:tap];
}

- (void)setUpContraints
{
    [self.contentScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.mas_equalTo(UIEdgeInsetsZero);
    }];
    [self layoutIfNeeded];
}

- (void)registerClass:(nonnull Class)cellClass forViewReuseIdentifier:(NSString *)identifier
{
    [self.viewClsDict setObject:NSStringFromClass(cellClass) forKey:identifier];
}

- (__kindof LBHorizontalLoopViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier
{
    for (LBHorizontalLoopViewCell *cell in self.reuseViews)
    {
        if ([cell.reuseIdentifier isEqualToString:identifier]) {
            return cell;
        }
    }
    
    Class cellCls = NSClassFromString(self.viewClsDict[identifier]);
    LBHorizontalLoopViewCell *cell = [[cellCls alloc] initWithReuseIdentifier:identifier];
    cell.userInteractionEnabled = NO;
    return cell;
}

#pragma mark - public

- (void)reloadDataAndStartRoll
{
    self.numberOfLoopView = [self.dataSource viewNumberOfLoopView:self];
    [self stopTimer];
    [self layoutCell];
    if (self.numberOfLoopView <= 1) {
        return;
    }
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer timerWithTimeInterval:self.duration + 0.2 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf timeHandler];
    }];
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addTimer:self.timer forMode:NSRunLoopCommonModes];
}

- (void)stopTimer
{
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    }
    _currentIndex = 0;
    [self.leftView removeFromSuperview];
    [self.middleView removeFromSuperview];
    [self.rightView removeFromSuperview];
    self.leftView = nil;
    self.middleView = nil;
    self.rightView = nil;
    [self.reuseViews removeAllObjects];
}

- (void)pause
{
    if (_timer) {
        [_timer setFireDate:[NSDate distantFuture]];
    }
}

- (void)proceed
{
    if (_timer) {
        [_timer setFireDate:[NSDate dateWithTimeInterval:self.duration sinceDate:[NSDate date]]];
    }
}

#pragma mark - private

- (void)layoutCell
{
    NSInteger count = self.numberOfLoopView;
    if (count == 1) {
        [self reloadLeftCell];
        self.contentScrollView.contentSize = CGSizeMake(CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
        return;
    }
    self.contentScrollView.contentSize = CGSizeMake(CGRectGetWidth(self.bounds) * 3, CGRectGetHeight(self.bounds));
    [self reloadLeftCell];
    [self reloadMiddleCell];
    [self reloadRightCell];
    self.contentScrollView.contentOffset = CGPointMake(CGRectGetWidth(self.bounds), 0);
}

- (void)reloadLeftCell
{
    NSInteger count = self.numberOfLoopView;
    NSInteger leftIndex = self.currentIndex - 1;
    if (leftIndex < 0) {
        ///如果是负数，说明当前中间的cell是第一个，则左边的应给是最后一个（为了达到无限轮播的效果）
        leftIndex = count - 1;
    }
    if (self.leftView) {
        ///移除原来的视图，并添加到重用池
        [self.leftView removeFromSuperview];
        [self.reuseViews addObject:self.leftView];
    }

    LBHorizontalLoopViewCell *leftCell = [self.dataSource cellForIndex:leftIndex inLoopView:self];
    leftCell.frame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
    self.leftView = leftCell;
    [self.contentScrollView addSubview:self.leftView];
    ///将当前使用的视图移除重用池
    [self.reuseViews removeObject:self.leftView];
}

- (void)reloadMiddleCell
{
    if (self.middleView) {
        ///移除原来的视图，并添加到重用池
        [self.middleView removeFromSuperview];
        [self.reuseViews addObject:self.middleView];
    }
    LBHorizontalLoopViewCell *cell = [self.dataSource cellForIndex:self.currentIndex inLoopView:self];
    self.middleView = cell;
    self.middleView.frame = CGRectMake(CGRectGetWidth(self.bounds), 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
    [self.contentScrollView addSubview:self.middleView];
    ///将当前使用的视图移除重用池
    [self.reuseViews removeObject:self.middleView];
}

- (void)reloadRightCell
{
    NSInteger count = self.numberOfLoopView;
    NSInteger rightIndex = self.currentIndex + 1;
    if (rightIndex > count - 1) {
        ///如果大于count - 1 ,说明当前中间cell是最后一个， 则右边应该展示第一个（为了达到无限轮播的效果）
        rightIndex = 0;
    }
    if (self.rightView) {
        ///移除原来的视图，并添加到重用池
        [self.rightView removeFromSuperview];
        [self.reuseViews addObject:self.rightView];
    }
    LBHorizontalLoopViewCell *rightCell = [self.dataSource cellForIndex:rightIndex inLoopView:self];
    rightCell.frame = CGRectMake(CGRectGetWidth(self.bounds) * 2, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
    self.rightView = rightCell;
    [self.contentScrollView addSubview:self.rightView];
    ///将当前使用的视图移除重用池
    [self.reuseViews removeObject:self.rightView];
}

- (void)timeHandler
{
    self.currentIndex ++;
    NSInteger count = self.numberOfLoopView;
    if (self.currentIndex == count) {
        self.currentIndex = 0;
    }
    [UIView animateWithDuration:0.4 animations:^{
        if (self.isDragging) {
            ///正在拖动的时候，避免尚未完成的动画对拖动造成干扰
            return;
        }
        [self.contentScrollView setContentOffset:CGPointMake(CGRectGetWidth(self.bounds) * 2, 0) animated:NO];
    } completion:^(BOOL finished) {
        if (self.isDragging) {
            ///正在拖动的时候，避免尚未完成的动画对拖动造成干扰
            return;
        }
        [self reloadAfterTranslation];
        if (self.delegate &&
            [self.delegate respondsToSelector:@selector(loopView:didScrollToCellAtIndex:)]) {
            [self.delegate loopView:self didScrollToCellAtIndex:self.currentIndex];
        }
    }];
}

- (void)reloadAfterTranslation
{
    ///先将左边的展示为当前索引的cell，在回复偏移量到中间
    [self reloadMiddleCell];
    self.contentScrollView.contentOffset = CGPointMake(CGRectGetWidth(self.bounds), 0);
//    [self.contentScrollView setContentOffset:CGPointMake(CGRectGetWidth(self.bounds), 0) animated:NO];
    ///回复偏移量之后，刷新左右两边的cell
    [self reloadLeftCell];
    [self reloadRightCell];
    if (self.style == LBHorizontalLoopViewStyleAlphaChange) {
        self.middleView.alpha = 1;
        self.leftView.alpha = 1;
        self.rightView.alpha = 1;
    }
}

#pragma mark - action

- (void)tapClick
{
    if (self.delegate &&
        [self.delegate respondsToSelector:@selector(loopView:didClickCellAtIndex:)]) {
        [self.delegate loopView:self didClickCellAtIndex:self.currentIndex];
    }
}

#pragma mark - UIScrollViewDelegate

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    self.isDragging = YES;
    [self pause];
    NSInteger count = self.numberOfLoopView;
    /*
     注意
     1这里如果 scrollView.contentOffset.x 不等于 CGRectGetWidth(scrollView.bounds)，说明
     开始拖动的时候scrollView还没有回归到中间位置，而我们每次都是
     在 scrollViewDidEndDecelerating 方法中进行修改当前索引并且重置回中间偏移量CGRectGetWidth(scrollView.bounds)的，
     这里偏移量 不是CGRectGetWidth(scrollView.bounds) ，说明拖动过之后， scrollViewDidEndDecelerating 还没有执行
     就立即进行下一次拖动了，这就需要我们手动调用一下 scrollViewDidEndDecelerating 要进行但尚未进行的的操作，保证我们的 当前展示数据正确。
     
     2这里不需要担心第二次的scrollViewWillBeginDragging 开始执行之后 ，上一次的 scrollViewDidEndDecelerating 执行问题
     因为经过验证发现，scrollViewWillBeginDragging 和 scrollViewDidEndDecelerating 并不是一对应的关系，
     就是我们快速滑动的时候，如果我们开始执行第二次的 scrollViewWillBeginDragging ，那么第一次 scrollViewDidEndDecelerating
     方法是会被跳过不执行的，所以我们不用担心 重复执行的问题
    */
    /* 当scrollView 的宽度不是整数的时候，这里会有精度丢失导致误差，
     所以这里添加0.5 的容错
     */
    if (scrollView.contentOffset.x > CGRectGetWidth(scrollView.bounds) + 0.5) {
        if (self.currentIndex < count - 1) {
            self.currentIndex ++;
        } else {
            self.currentIndex = 0;
        }
        [self reloadAfterTranslation];
    } else if (scrollView.contentOffset.x < CGRectGetWidth(scrollView.bounds) - 0.5) {
        if (self.currentIndex > 0) {
            self.currentIndex --;
        } else {
            self.currentIndex = count - 1;
        }
        [self reloadAfterTranslation];
    }
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (self.delegate &&
        [self.delegate respondsToSelector:@selector(loopView:didScroll:)]) {
        [self.delegate loopView:self didScroll:scrollView];
    }
    if (self.style == LBHorizontalLoopViewStyleAlphaChange) {
        [self changeAlphaWhenScrollViewDidScroll:scrollView];
    }
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (self.restartTimeEndingDragging) {
        [self proceed];
    }
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    NSInteger count = self.numberOfLoopView;
    /*
     遇到这样一中情况，scrollView.contentoffset.x = 717.333333;
     CGRectGetWidth(self.bounds)  =  358.800001,
     这时候其实已经移动到第三个视图了，这时候index 应该等于2
     但是由于小数取值的精度问题，导致了index = 1,
     所以这里为了准确 添加了0.5
     */
    ///这里为了避免由于精度的误差导致的计算错误，我们加了0.5
    NSInteger index = (scrollView.contentOffset.x + 0.5) / CGRectGetWidth(self.bounds);

    if (index == 2) {
        if (self.currentIndex < count - 1) {
            self.currentIndex ++;
        } else {
            self.currentIndex = 0;
        }
    } else if (index == 0) {
        if (self.currentIndex > 0) {
            self.currentIndex --;
        } else {
            self.currentIndex = count - 1;
        }
    }
    [self reloadAfterTranslation];
    if (self.delegate &&
        [self.delegate respondsToSelector:@selector(loopView:didScrollToCellAtIndex:)]) {
        [self.delegate loopView:self didScrollToCellAtIndex:self.currentIndex];
    }
    self.isDragging = NO;
}

#pragma mark - util
- (void)changeAlphaWhenScrollViewDidScroll:(UIScrollView *)scrollView
{
    CGFloat scroW = scrollView.frame.size.width;
    CGFloat pointX = scrollView.contentOffset.x;
    ///滚动距离绝对值
    CGFloat marginX = fabs(scroW-scrollView.contentOffset.x);
    float scroProgress = marginX/scroW;
    if (pointX==scroW) {
        
    } else {
        if (pointX>scroW) {
            self.middleView.alpha =1- scroProgress-0.25;
            self.rightView.alpha = scroProgress;
        } else {
            self.middleView.alpha =1- scroProgress-0.25;
            if (scroProgress>0.5) {
                self.leftView.alpha = scroProgress;
            }else{
                self.leftView.alpha = scroProgress-0.25;
            }
        }
    }
}

#pragma mark - lazy load

- (UIScrollView *)contentScrollView
{
    if (!_contentScrollView) {
        _contentScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds))];
        _contentScrollView.contentSize = CGSizeMake(3 * CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
        _contentScrollView.delegate = self;
        _contentScrollView.pagingEnabled = YES;
        _contentScrollView.showsHorizontalScrollIndicator = NO;
        _contentScrollView.bounces = NO;
    }
    return _contentScrollView;
}

- (NSMutableDictionary *)viewClsDict
{
    if (!_viewClsDict) {
        _viewClsDict = [[NSMutableDictionary alloc] init];
    }
    return _viewClsDict;
}

- (NSMutableArray *)reuseViews
{
    if (!_reuseViews) {
        _reuseViews = [NSMutableArray array];
    }
    return _reuseViews;
}

@end
